package andrei.mower2.io.file;

import java.io.BufferedReader;
import java.io.IOException;

import org.apache.commons.lang3.EnumUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import andrei.mower2.business.Mower;
import andrei.mower2.business.Mower.Command;
import andrei.mower2.business.Mower.Direction;
import andrei.mower2.business.MowerVector;
import andrei.mower2.business.Position;
import andrei.mower2.io.MowerInputListener;
import andrei.mower2.io.MowerInputReader;
import andrei.mower2.io.exceptions.MissingLawnCornerException;
import andrei.mower2.io.exceptions.UnparsableCommandException;
import andrei.mower2.io.exceptions.UnparsableLawnCornerException;
import andrei.mower2.io.exceptions.UnparsableMowerStartVectorException;
import andrei.mower2.io.file.exceptions.UnreadableCharacterException;
import andrei.mower2.io.file.exceptions.UnreadableLineException;
import andrei.mower2.main.i18n.MessageUtils;

public class MowerFileInputReader implements MowerInputReader {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(MowerFileInputReader.class);
	
	private final BufferedReader bufferedReader;
	private long currentLineNumber;
	private long currentCharacterNumber;
	
	private MowerInputListener mowerInputListener;
	
	public MowerFileInputReader(BufferedReader bufferedReader) throws IOException {
		this.bufferedReader = bufferedReader;
		currentCharacterNumber = -1;
	}

	@Override
	public Position getLawnCorner() {
		String line = null;
		try {
			line = nextLine();			
		} catch(UnreadableLineException e) {
			throw new UnparsableLawnCornerException(MessageUtils.getMessage("error.io.unparsableLawnCornerLine", line, e));
		}
		validateLawnCornerLine(line);
		return parseLawnCornerLine(line);
		
	}

	@Override
	public MowerVector nextStartVector() throws UnparsableMowerStartVectorException {
		advanceToNextMowerStartVectorLine();
		
		String line = null;
		try {
			line = nextLine();
		} catch(UnreadableLineException e) {
			throw new UnparsableMowerStartVectorException(currentLineNumber, line, e);
		}
		if(line == null) {
			this.mowerInputListener.onMowersEnded();
			return null;
		}
		
		return parseStartVectorLine(line);
	}

	@Override
	public Command nextCommand() throws UnparsableCommandException {
		advanceToNextCommandsLine();
		
		Character commandChar = null;
		try {
			commandChar = nextChar();
		} catch (UnreadableCharacterException e) {
			throw new UnparsableCommandException(currentLineNumber, currentCharacterNumber, commandChar, e);
		}
		
		if(commandChar == null) {
			mowerInputListener.onCurrentMowerCommandsEnd();
			mowerInputListener.onMowersEnded();
			return null;
		}
		
		if(commandChar == System.lineSeparator().charAt(0)) {
			mowerInputListener.onCurrentMowerCommandsEnd();
			return null;
		}				
		
		return parseCommandChar(commandChar);
	}
	
	@Override
	public void addListener(MowerInputListener inputListener) {
		mowerInputListener = inputListener;
	}
	

	@Override
	public void close() throws Exception {
		LOGGER.debug("Closing buffered reader...");
		try {
			bufferedReader.close();
		} catch(IOException e) {
			LOGGER.error(MessageUtils.getMessage("error.io.unableToCloseFile"), e);
		}
		LOGGER.debug("Buffered reader closed.");
	}
	
	private void advanceToNextMowerStartVectorLine() {
		if(currentLineNumber % 2 == 0) {
			try {
				nextLine();
			} catch(UnreadableLineException e) {
				LOGGER.error(e.getMessage(), e);
			}
		}
	}
	
	private void advanceToNextCommandsLine() {
		if(currentLineNumber % 2 != 0) {
			try {
				nextLine();
			} catch(UnreadableLineException e) {
				LOGGER.error(e.getMessage(), e);
			}
		}
	}

	private String nextLine() throws UnreadableLineException {
		String line = null;
		try {
			line = bufferedReader.readLine();
		} catch(IOException e) {
			throw new UnreadableLineException(currentLineNumber, line, e);
		}
		if(line != null) {
			currentLineNumber++;
		}
		return line;
	}
	
	private Character nextChar() throws UnreadableCharacterException {
		int charRaw = -1;
		try {
			charRaw = bufferedReader.read();
			if(charRaw == System.lineSeparator().charAt(0)) {
				 for(int i = 0; i < System.lineSeparator().length()-1; i++) {
					 bufferedReader.read();
				 }
				 currentLineNumber++;
				 currentCharacterNumber = -1;
			}
		} catch (IOException e) {
			throw new UnreadableCharacterException(currentLineNumber, currentCharacterNumber , e);
		}
		if(charRaw == -1) {
			return null;
		}
		Character c = (char) charRaw;
		currentCharacterNumber++;
		return c;
	}
	
	private void validateLawnCornerLine(String line) throws MissingLawnCornerException {
		if(line == null) {
			throw new MissingLawnCornerException();
		}
	}
	
	private Position parseLawnCornerLine(String line) {
		String[] arr = line.split(" ");
		if(arr.length != 2) {
			throw new UnparsableLawnCornerException(MessageUtils.getMessage("error.io.unparsableLawnCornerLine", line));
		}
		int x = 0;
		try {
			x = Integer.parseInt(arr[0]);			
		} catch(NumberFormatException e) {
			throw new UnparsableLawnCornerException(MessageUtils.getMessage("error.io.unparsableLawnCornerLine", line), e); 
		}
		int y = 0;
		try {
			y = Integer.parseInt(arr[1]);			
		} catch(NumberFormatException e) {
			throw new UnparsableLawnCornerException(MessageUtils.getMessage("error.io.unparsableLawnCornerLine", line), e); 
		}
		return new Position(x, y);
	}
	
	private MowerVector parseStartVectorLine(String line) throws UnparsableMowerStartVectorException {
		String[] arr = line.split(" ");
		if(arr.length != 3) {
			throw new UnparsableMowerStartVectorException(currentLineNumber, line);
		}
		
		int x = 0;
		try {
			x = Integer.parseInt(arr[0]);			
		} catch(NumberFormatException e) {
			throw new UnparsableMowerStartVectorException(currentLineNumber, line, e); 
		}
		int y = 0;
		try {
			y = Integer.parseInt(arr[1]);			
		} catch(NumberFormatException e) {
			throw new UnparsableMowerStartVectorException(currentLineNumber, line, e);
		}
		
		if(!EnumUtils.isValidEnum(Direction.class, arr[2])) {
			throw new UnparsableMowerStartVectorException(currentLineNumber, line);
		}
		Direction direction = Mower.Direction.valueOf(arr[2]);
		
		return new MowerVector(new Position(x, y), direction);
	}
	
	private Command parseCommandChar(Character c) throws UnparsableCommandException {
		if(!EnumUtils.isValidEnum(Command.class, c.toString())) {
			throw new UnparsableCommandException(currentLineNumber, currentCharacterNumber, c); 
		}
		return Mower.Command.valueOf(c.toString());
	}

}
